/*
 * VfatFactory.c
 *
 *  Created on: Apr 29, 2010
 *      Author: aemagulov
 */

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#define ATTR_READ_ONLY	0x01
#define ATTR_HIDDEN		0x02
#define ATTR_SYSTEM		0x04
#define ATTR_VOLUME_ID	0x08
#define ATTR_DIRECTORY	0x10
#define ATTR_ARCHIVE	0x20
#define ATTR_DEVICE		0x40
#define ATTR_LONG_NAME	(ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)

#define FAT_STOP_SEARCH	0x0
#define FAT_JAPAN_CHAR	0x5
#define FAT_ENTRY_FREE	0xE5

#define LFN_INVALID		0x80
#define LFN_LAST_LFN	0x40
#define LFN_MASK		0x1f

#define FAT_FLAG_DIR	((1 << 4))


#define FAT16_SIZ_OFF	22
#define FAT32_SIZ_OFF	36
#define FAT_ROOT_ENT_CNT_OFF	17
#define FAT_BYTS_PER_SECT_OFF	11
#define FAT_SEC_PER_CLUS_OFF	13
#define FAT_NUM_FATS_OFF	16
#define FAT_RESRVD_SECTRS_OFF	14
#define FAT16_TOT_SEC_OFF	19
#define FAT32_TOT_SEC_OFF	32
#define FAT32_ROOT_CLUS_OFF	44
#define SEC_FROM_CLUS(__clus,secpreclus,firstdatasec) \
		(((__clus - 2) * secpreclus) + firstdatasec)


int find_entry(unsigned int rootclus, char *name);

typedef enum {
		eFAT12 = 1,
		eFAT16,
		eFAT32
} FatType_t;

/**
 *
 */
#ifdef USE_MMAP
static char *buf; /* memory mapped file pointer */
#endif
static unsigned int BytsPerSec;
static unsigned int SecPerClus;
static unsigned int TotSec;
static unsigned int RsvdSecCnt;
static unsigned int NumFATs;
static unsigned int FATSz;
static FatType_t 	fatType;
static unsigned int FirstRootDirSect;
static unsigned int FirstDataSector;
static unsigned int fat1;
static char *lfnstr = 0, *_lfnstr = 0;
static int fd;


/**
 *
 */
static
unsigned int
read8abs(unsigned int off)
{
	return (unsigned int)(*(unsigned char *)(off) & 0xff);
}

/**
 *
 */
static
unsigned int
read32abs(unsigned int off)
{
	return (unsigned int)*(unsigned int *)(off);
}

/**
 *
 */
static
unsigned int
read16abs(unsigned int off)
{
#if 1
	unsigned int res = 0;
	res = ((char *)off)[1] & 0xff;
	res <<= 8;
	res |= (((char *)off)[0] & 0xff);
	return res;
#else
	return (unsigned int)*(unsigned short *)(off);
#endif
}


/**
 *
 */
static
unsigned int
read32(unsigned int off)
{
	unsigned int res;
#ifdef USE_MMAP
	res = read32abs((unsigned int)(buf + off));
#else
	lseek(fd,off,SEEK_SET);
	read(fd,&res,4);
#endif
	return res;
}

/**
 *
 */
static
unsigned int
read16(unsigned int off)
{
	unsigned int res;
#ifdef USE_MMAP
	res = read16abs((unsigned int)(buf + off));
#else
	lseek(fd,off,SEEK_SET);
	read(fd,&res,2);
#endif
	res &= 0xffff;
	return res;
}

/**
 *
 */
static
unsigned int
read8(unsigned int off)
{
	unsigned int res;
#ifdef USE_MMAP
	res = read8abs((unsigned int)(buf + off));
#else
	lseek(fd,off,SEEK_SET);
	read(fd,&res,1);
#endif
	res &= 0xff;
	return res;
}

/**
 *
 */
static
unsigned int
read_clust(unsigned int clu)
{
	unsigned int r1,r2 = 0xfff8;

	if(fatType == eFAT32)
		r2 = (unsigned int)read32(fat1 + clu * 4);

	else if(fatType == eFAT16)
		r2 = (unsigned int)read16(fat1 + clu * 2);

	else if(fatType == eFAT12)
	{
		r1 = clu + clu / 2;
		r2 = read16(fat1 + r1);

		if ((clu % 2)) // odd
			r2 >>= 4;
		r2 &= 0xfff;
	}

	return r2;
}

/**
 *
 */
static int
read_dir_entry(
		char *__to,
		unsigned int clust,
		unsigned int off, unsigned int siz)
{
	int len = 0;
	unsigned int BytsPerClust;
	unsigned int fEntrySect;
	unsigned int offst;
	unsigned int curClust;
	unsigned int curBytsOff;
	unsigned int curSectOff;
	unsigned int curSectBytsOff;

	// ERROR
	if (!__to || !siz) return -1;

	// initialize values
	BytsPerClust = BytsPerSec * SecPerClus;
	curClust = off / BytsPerClust;
	curBytsOff = off % BytsPerClust;
	curSectOff = curBytsOff / BytsPerSec;
	curSectBytsOff = curBytsOff % BytsPerSec;

	// find starting cluster
	while(curClust--)
	{
		clust = read_clust(clust);
		if ((clust & 0xfff) >= 0xff8)
			return 0;
	}

	fEntrySect = SEC_FROM_CLUS(clust,SecPerClus,FirstDataSector);
	offst = fEntrySect * BytsPerSec + curBytsOff;

	// copy bytes
	while (siz-- && ((clust & 0xfff) < 0xff8))
	{
		++len;
		*__to++ = read8(offst++);
		if (!(offst % BytsPerClust))
		{
			clust = read_clust(clust);
			fEntrySect = SEC_FROM_CLUS(clust,SecPerClus,FirstDataSector);
			offst = fEntrySect * BytsPerSec;
		}
	}

	return len;
}


/**
 * parse next entry
 */
static void
onEntry (char *entry, char *name)
{
	char buffer[128];
	int entrylen, len;
	int j;
	int cl = -1;
	int lfn = 0;
	int res2;

	// break on end
	if (entry[0] == 0x0)
		return;

	/*
	 *  do filter attributes
	 */
	// skip device entries
	if (entry[11] & 0x40)
		goto do_next;

	// Do LFN entries
	if ((entry[11] & ATTR_LONG_NAME))
	{
		// skip deleted LFN entries
		if (entry[0] & LFN_INVALID)
			goto do_lfn_next;

		// number of lfn entries
		lfn = entry[0] & LFN_MASK;

		// reset lfn string
		if (entry[0] & LFN_LAST_LFN)
		{
			// reset lfn
			if (_lfnstr)
				free(_lfnstr);
			_lfnstr = malloc(13 * lfn + 1); /* last LFN */
			lfnstr = _lfnstr + 13 * lfn;
			*lfnstr = 0;
		}

		/* TODO define a function */
		for (j = 31; j >= 28; j--)
				*--lfnstr = entry[--j];

		for (j = 25; j >= 14; j--)
				*--lfnstr = entry[--j];

		for (j = 10; j >= 1; j--)
				*--lfnstr = entry[--j];

		goto do_lfn_next;
	}

	if (!_lfnstr)
	{
		_lfnstr = lfnstr = malloc(13);
		/* TODO define a function */
		for(j = 0; j < 8; j++)
			if (entry[j] != ' ') *lfnstr++ = entry[j];
		*lfnstr++ = '.';
		for(j = 8; j < 11; j++)
			if (entry[j] != ' ') *lfnstr++ = entry[j];
		*lfnstr = 0;
	}

	// skip deleted items
	if ((unsigned char)entry[0] == 0xe5)
		goto do_next;

	// replace character
	if (entry[0] == 0x5)
		entry[0] = 0xe5;

	// skip non reguler items
	if (!(entry[11] & ATTR_DIRECTORY)
		&& !(entry[11] & ATTR_ARCHIVE))
		goto do_next;

	if (!strncasecmp(_lfnstr,name,strlen(name)))
	{
		cl = read16abs((unsigned int)(entry + 20));
		cl <<= 16;
		cl |= read16abs((unsigned int)(entry + 26));

		if (entry[11] & ATTR_ARCHIVE)
		{
			printf("ENTRY (%s) = %u : ", _lfnstr, cl);

			len = 128;
			entrylen = read32abs((unsigned int)(entry + 28));
			j = 0;
//			printf("buffer(0x%x), cl(%u), j(%d), len(%d)\n",(unsigned int)buffer,cl, j, len);
			while ((res2 = read_dir_entry(buffer, cl, j, len)) && len)
			{
				write(2, buffer, res2);
				j += res2;
				entrylen -= res2;
				len = entrylen < len ? entrylen : len;
			}
		}
		else
			if (entry[11] & ATTR_DIRECTORY)
			{
				// reset LFN
				if (_lfnstr)
					free(_lfnstr);
				_lfnstr = 0;
				find_entry(cl,name);
			}
	}
	else
	{
		printf("%s\n", _lfnstr);
	}

do_next:
	// reset LFN
	if (_lfnstr)
		free(_lfnstr);
	_lfnstr = 0;

do_lfn_next:
	return;
}

/**
 * Scans VFAT filesystem for files.
 */
void
list_vfat(char *name)
{
	char entry[0x20];
	int offst, i, j;
	int RootDirSectors;
	int DataSec;
	int CountofClusters;

	do
	{

		FATSz = (unsigned int)read16(FAT16_SIZ_OFF);
		if (!FATSz)
		{
			fatType = eFAT32;
			FATSz = read32(FAT32_SIZ_OFF);
		}
		else
			fatType = eFAT16;

		NumFATs = read8(FAT_NUM_FATS_OFF);
		RsvdSecCnt = read16(FAT_RESRVD_SECTRS_OFF);
		BytsPerSec = read16(FAT_BYTS_PER_SECT_OFF);
		SecPerClus = read8(FAT_SEC_PER_CLUS_OFF);
		RootDirSectors =
				((read16(FAT_ROOT_ENT_CNT_OFF) * 32)
						+ BytsPerSec - 1)
							/ BytsPerSec;

		FirstDataSector = RsvdSecCnt + (NumFATs	* FATSz)
								+ RootDirSectors;

		if ((fatType == eFAT32))
		{
			TotSec = read32(FAT32_TOT_SEC_OFF);
			printf("Assuming FAT32 FS, TotSec = %d\n", (int)TotSec);
		}
		else
			TotSec = read16(FAT16_TOT_SEC_OFF);

		DataSec = TotSec - FirstDataSector;
		CountofClusters = DataSec / SecPerClus;

		if(CountofClusters < 4085)
		{
			fatType = eFAT12;
			printf("--------- FAT 12 ---------\n");
		}
		else
			if(CountofClusters < 65525)
			{
				fatType = eFAT16;
				printf("--------- FAT 16 ---------\n");
			}
		else
		{
			fatType = eFAT32;
			printf("--------- FAT 32 ---------\n");
		}

		fat1 = BytsPerSec * RsvdSecCnt;

		printf("RsvdSecCnt = %u\n"
			"RootDirSectors = %u\n"
			"FirstDataSector = %u\n"
			"DataSec = %u\n"
			"TotSec = %u\n"
			"CountofClusters = %u\n"
			"SecPerClus = %u\n"
			"First FAT starts at %u\n"
				,
			RsvdSecCnt, RootDirSectors,
				FirstDataSector, DataSec, TotSec
				, CountofClusters,SecPerClus,
				fat1);



		if (fatType != eFAT32)
		{
			/*
			 * TODO This must be a function
			 */
			i = 0;
			j = 0;
			FirstRootDirSect = (unsigned int)(RsvdSecCnt + NumFATs * FATSz);
			offst = FirstRootDirSect * BytsPerSec;

			do
			{
				for (j = 0; j < 0x20; j++) entry[j] = read8(offst++);;
				// prepare next step
				i += 0x20;
				onEntry(entry, name);
			} while (entry[0]);

		}
		else
			find_entry(read32(FAT32_ROOT_CLUS_OFF),name);

	} while (0);

}


/**
 * Return starting cluster of entry with name 'name'
 */
int
find_entry(unsigned int rootclus, char *name)
{
	char entry[0x20];
	int cl = -1;
	int res;
	int i = 0;

	while ((res = read_dir_entry(entry, rootclus, i, 0x20)) == 0x20)
	{
		// prepare next step
		i += 0x20;
		onEntry(entry, name);
		continue;
	}

	return cl;
}

/**
 *
 */
int main(int argc, char **argv)
{
	struct stat f_stat; /* fstat struct */

	fd = open(argv[1], O_RDWR);

	if (fd < 0)
	{
		printf("Unable to open() %s file\n", argv[1]);
		exit(-1);
	}

	fstat(fd, &f_stat);

#ifdef USE_MMAP
	buf = mmap(0, f_stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

	if (buf <= 0)
	{
		printf("Unable to mmap() %s file\n", argv[1]);
		goto do_exit;
	}
#endif

	list_vfat(argv[2]);

do_exit:
	close(fd);
	exit(0);
}
